package phototagger;

import java.awt.Image;
import java.awt.Panel;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import phototagger.imgsim.WaveletSimilarityFinder;
import phototagger.imgsim.NaiveSimilarityFinder;
import phototagger.imgsim.CompressionSimilarityFinder;
import java.util.LinkedHashMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.List;
import java.util.Map;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.Collections;

//Java class demonstrating access to the Flickr API Web service
public class QueryFlickr {
	
	// The Flickr API application ID (please change in order to use your own)
	private static String appid = "bf5e51500f8f17b9bd8fded4fb8c526d";

	// The minimum upload date for a photo
	private static long timestamp;
	{ try { timestamp = new java.text.SimpleDateFormat ("MM/dd/yyyy HH:mm:ss").parse("01/01/2005 01:00:00").getTime(); } catch ( Exception e ) {} };
	
	// The maximum number of photos to be retrieved in each call
	private static int resultsPerPage = 250;
	
	// A comparator used to sort the entries in a map according to the number of items in each value
	public static Map<String, String[]> sortByValue( Map<String, String[]> map ) {
		List<Map.Entry<String, String[]>> list = new LinkedList<Map.Entry<String, String[]>>( map.entrySet() );
		Collections.sort( list, new Comparator<Map.Entry<String, String[]>>() {
			   public int compare (Map.Entry<String,String[]> e1, Map.Entry<String,String[]> e2) {
				      int cf = new Integer(e2.getValue().length).compareTo(new Integer(e1.getValue().length));
				      if (cf == 0) cf = e1.getKey().compareTo(e2.getKey());
				      return cf;
			   }
		} );
		Map<String, String[]> result = new LinkedHashMap<String, String[]>();
		for (Map.Entry<String, String[]> entry : list) {
			result.put( entry.getKey(), entry.getValue() );
		}
		return result;
	}
		
	public static Map<String, Integer> sortByIntegerValue( Map<String, Integer> map ) {
		List<Map.Entry<String, Integer>> list = new LinkedList<Map.Entry<String, Integer>>( map.entrySet() );
		Collections.sort( list, new Comparator<Map.Entry<String, Integer>>() {
			   public int compare (Map.Entry<String,Integer> e1, Map.Entry<String,Integer> e2) {
				      int cf = new Integer(e2.getValue()).compareTo(new Integer(e1.getValue()));
				      if (cf == 0) cf = e1.getKey().compareTo(e2.getKey());
				      return cf;
			   }
		} );
		Map<String, Integer> result = new LinkedHashMap<String, Integer>();
		for (Map.Entry<String, Integer> entry : list) {
			result.put( entry.getKey(), entry.getValue() );
		}
		return result;
	}

	// Print the contents of an XML document to the standard output
	public static String printXML ( Document doc ) throws TransformerException {
		return printXML(doc.getDocumentElement());
	}
	
	// Print the contents of an XML element to the standard output
	public static String printXML ( Element element ) throws TransformerException {
		 TransformerFactory transfac = TransformerFactory.newInstance();
	     Transformer trans = transfac.newTransformer();
	     trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
	     trans.setOutputProperty(OutputKeys.INDENT, "yes");
	     StringWriter sw = new StringWriter();
	     StreamResult result = new StreamResult(sw);
	     DOMSource source = new DOMSource(element);
	     trans.transform(source, result);
	     String xmlString = sw.toString();
	     System.out.println(xmlString);
	     return xmlString;
	}
	
	// Print the contents of a map to the standard output
	public static void printMap ( Map<String,?> map ) {
		if (map instanceof LinkedHashMap<?,?>) {
			for ( String aux : ((LinkedHashMap<String,?>)map).keySet() ) {
				System.out.print( aux +  " -> ");
				if (map.get(aux) instanceof Integer || map.get(aux) instanceof Double) System.out.print(map.get(aux));
				else for ( String aux2 : (String[])(map.get(aux)) ) System.out.print(aux2 + " "); 
				System.out.println();
			}
		} else if (map instanceof HashMap<?,?>) {
			for ( String aux : map.keySet() ) System.out.println( aux +  " -> " + map.get(aux));
		}
	}
	
	// Send an HTTP POST request and return result as an XML document
	public static Document httpPost ( String location, String[] params ) {
		try {
			String data = "";
			for ( int i = 0; i < params.length; i++) {
				if ( data.length() > 0 ) data += "&";
				data += URLEncoder.encode(params[i], "UTF-8") + "=" + URLEncoder.encode(params[++i], "UTF-8");
			}		
			URL url = new URL(location);
			URLConnection conn = url.openConnection ();
		    conn.setDoOutput(true);
		    OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream());
		    wr.write(data);
		    wr.flush();
			BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
			StringBuffer sb = new StringBuffer();
			while ((data = rd.readLine()) != null) sb.append(data);
			rd.close();			
			DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
	        DocumentBuilder db = dbf.newDocumentBuilder();
	        InputSource is = new InputSource();
	        is.setCharacterStream(new StringReader(sb.toString()));
	        Document doc = db.parse(is);
			return doc;
		} catch ( Exception e ) {
			return null;
		}
	}

	// Send an HTTP GET request and return result as an XML document
	public static Document httpGet ( String location ) {
		try {
			URL url = new URL(location);
			URLConnection conn = url.openConnection ();
			BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
			StringBuffer sb = new StringBuffer();
			String line;
			while ((line = rd.readLine()) != null) sb.append(line);
			rd.close();
			DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
	        DocumentBuilder db = dbf.newDocumentBuilder();
	        InputSource is = new InputSource();
	        is.setCharacterStream(new StringReader(sb.toString()));
	        Document doc = db.parse(is);
			return doc;
		} catch ( Exception e ) {
			return null;
		}
	}

	public static boolean isValidTag ( String tag ) {
		Set<String> stopwords = new HashSet<String>();
		stopwords.add("photo");
		stopwords.add("exif");
		stopwords.add("gps");
		stopwords.add("test");
		stopwords.add("flickr");
		stopwords.add("geotagged");
		if ( tag == null ) return false;
		tag = tag.trim();
		if (tag.contains(":") || tag.contains("=") || tag.length()==0 || tag.length()==0) return false;
		if ( stopwords.contains(tag)) return false;
		return true;
	}
	
	// Query Flickr for photos within a given search radius of a geospatial point
	public static Document queryFlickr ( String point, double radius ) {
		String aux[] = point.substring(point.indexOf("(")+1,point.indexOf(")")).split(",");
		double longitude = new Double(aux[0].trim());
		double latitude = new Double(aux[1].trim());
		return queryFlickr(longitude,latitude,radius);
	}

	// Query Flickr for photos with a given search radius of a geospatial point
	public static Document queryFlickr ( double longitude, double latitude, double radius ) {
		if (radius > 32 ) radius = 32;
		String url = "http://api.flickr.com/services/rest/?method=flickr.photos.search&lat=" + latitude + "&lon=" + longitude + "&radius=" + radius + "&radius_units=km&tag_mode=all&content_type=1&has_geo=1&per_page=" + resultsPerPage + "&page=1&extras=description,owner_name,geo,tags,views,url_o&min_upload_date=" + timestamp + "&api_key=" + appid;
		return httpGet(url);
	}

	// Query Flickr for photos having a given descriptive tag
	public static Document queryFlickr ( String tag ) {
		String url = "http://api.flickr.com/services/rest/?method=flickr.photos.search&tags=" + tag + "&tag_mode=all&content_type=1&has_geo=1&geo_context=2&per_page=" + resultsPerPage + "&page=1&extras=description,owner_name,geo,tags,views,url_o&min_upload_date=" + timestamp + "&api_key=" + appid;
		return httpGet(url);
	}

	// Take the results of a Flickr query and return a map associating photo URLs to the corresponding number of views
	public static Map<String,Integer> photosToViews ( Document doc ) throws XPathExpressionException {
		Map<String,Integer> results = new HashMap<String,Integer>();
		String xpathStr = "//photo";
		XPath xpath = XPathFactory.newInstance().newXPath();
		XPathExpression expr = xpath.compile(xpathStr);
		NodeList elemList = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
		for ( int i = 0; i < elemList.getLength(); i++ ) {
			String id = xpath.compile("./@url_o").evaluate(elemList.item(i));
			if( id.trim().length()==0 ) continue;
			Integer views = new Integer(xpath.compile("./@views").evaluate(elemList.item(i)));
			results.put(id,views);
		}
		return sortByIntegerValue(results);
	}
	
	// Take the results of a Flickr query and return a map associating photo URLs to the corresponding tags
	public static Map<String,String[]> photosToTags ( Document doc ) throws XPathExpressionException {
		Map<String,String[]> results = new HashMap<String,String[]>();
		String xpathStr = "//photo";
		XPath xpath = XPathFactory.newInstance().newXPath();
		XPathExpression expr = xpath.compile(xpathStr);
		NodeList elemList = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
		for ( int i = 0; i < elemList.getLength(); i++ ) {
			String id = xpath.compile("./@url_o").evaluate(elemList.item(i));
			if( id.trim().length()==0 ) continue;
			String tags[] = xpath.compile("./@tags").evaluate(elemList.item(i)).split(" +");
			Set<String> filteredTags = new HashSet<String>();
			for ( String tag : tags ) {
				if (!isValidTag(tag)) continue;
				filteredTags.add(tag);
			}
			results.put(id,filteredTags.toArray(new String[0]));
		}
		return sortByValue(results);
	}

	// Take the results of a Flickr query and return a map associating photo URLs to the corresponding owner IDs
	public static Map<String,String> photosToUsers ( Document doc ) throws XPathExpressionException {
		Map<String,String> results = new HashMap<String,String>();
		String xpathStr = "//photo";
		XPath xpath = XPathFactory.newInstance().newXPath();
		XPathExpression expr = xpath.compile(xpathStr);
		NodeList elemList = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
		for ( int i = 0; i < elemList.getLength(); i++ ) {
			String id = xpath.compile("./@url_o").evaluate(elemList.item(i));
			if( id.trim().length()==0 ) continue;
			String owner = xpath.compile("./@owner").evaluate(elemList.item(i));
			results.put(id,owner);
		}
		return results;
	}

	// Take the results of a Flickr query and return a map associating photo URLs to the corresponding geospatial points
	public static Map<String,String> photosToPoints ( Document doc ) throws XPathExpressionException {
		Map<String,String> results = new HashMap<String,String>();
		String xpathStr = "//photo";
		XPath xpath = XPathFactory.newInstance().newXPath();
		XPathExpression expr = xpath.compile(xpathStr);
		NodeList elemList = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
		for ( int i = 0; i < elemList.getLength(); i++ ) {
			String id = xpath.compile("./@url_o").evaluate(elemList.item(i));
			if( id.trim().length()==0 ) continue;
			String latitude = xpath.compile("./@latitude").evaluate(elemList.item(i));
			String longitude = xpath.compile("./@longitude").evaluate(elemList.item(i));
			results.put(id,"POINT(" + longitude + " " + latitude + ")");
		}
		return results;
	}

	// Take the results of a Flickr query and return a map associating tags to the corresponding photo URLs
	public static Map<String,String[]> tagsToPhotos ( Document doc ) throws XPathExpressionException {		
		Map<String,String[]> result = new HashMap<String,String[]>();
		Map<String,String[]> photosToTags = photosToTags(doc);
		for ( String photo : photosToTags.keySet() ) {
			for ( String tag : photosToTags.get(photo)) {
				Set<String> auxSet = new HashSet<String>();
				String aux[] = result.get(tag);
				if ( aux!=null ) for ( String np : aux ) auxSet.add(np);
				auxSet.add(photo);
				result.put(tag,auxSet.toArray(new String[0]));
			}
		}
		return sortByValue(result);
	}

	// Take the results of a Flickr query and return a map associating tags to the user IDs that have used them
	public static Map<String,String[]> tagsToUsers ( Document doc ) throws XPathExpressionException {		
		Map<String,String[]> result = new HashMap<String,String[]>();
		String xpathStr = "//photo";
		XPath xpath = XPathFactory.newInstance().newXPath();
		XPathExpression expr = xpath.compile(xpathStr);
		NodeList elemList = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
		for ( int i = 0; i < elemList.getLength(); i++ ) {
			String owner = xpath.compile("./@owner").evaluate(elemList.item(i));
			String tags[] = xpath.compile("./@tags").evaluate(elemList.item(i)).split(" +");
			for ( String tag : tags) {
				if (!isValidTag(tag)) continue;
				Set<String> auxSet = new HashSet<String>();
				String aux[] = result.get(tag);
				if ( aux!=null ) for ( String np : aux ) auxSet.add(np);
				auxSet.add(owner);
				result.put(tag, auxSet.toArray(new String[0]));
			}
		}
		return sortByValue(result);
	}

	// Take the results of a Flickr query and return a map associating tags to the total number views associated to their corresponding photos
	public static Map<String,Integer> tagsToViews ( Document doc ) throws XPathExpressionException {		
		Map<String,Integer> result = new HashMap<String,Integer>();
		String xpathStr = "//photo";
		XPath xpath = XPathFactory.newInstance().newXPath();
		XPathExpression expr = xpath.compile(xpathStr);
		NodeList elemList = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
		for ( int i = 0; i < elemList.getLength(); i++ ) {
			Integer views = new Integer(xpath.compile("./@views").evaluate(elemList.item(i)));
			String tags[] = xpath.compile("./@tags").evaluate(elemList.item(i)).split(" +");
			for ( String tag : tags) {
				if (!isValidTag(tag)) continue;
				Integer aux = result.get(tag);
				if ( aux==null ) aux = new Integer(0);
				aux += views;
				result.put(tag, aux);
			}
		}
		return sortByIntegerValue(result);
	}

	// Take the results of a Flickr query and return a map associating tags to the corresponding geospatial points
	public static Map<String,String[]> tagsToPoints ( Document doc ) throws XPathExpressionException {
		Map<String,String[]> result = new HashMap<String,String[]>();
		String xpathStr = "//photo";
		XPath xpath = XPathFactory.newInstance().newXPath();
		XPathExpression expr = xpath.compile(xpathStr);
		NodeList elemList = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
		for ( int i = 0; i < elemList.getLength(); i++ ) {
			String latitude = xpath.compile("./@latitude").evaluate(elemList.item(i));
			String longitude = xpath.compile("./@longitude").evaluate(elemList.item(i));
			String point = "POINT(" + longitude + " " + latitude + ")";
			String tags[] = xpath.compile("./@tags").evaluate(elemList.item(i)).split(" +");
			for ( String tag : tags) {
				if (!isValidTag(tag)) continue;
				Set<String> auxSet = new HashSet<String>();
				String aux[] = result.get(tag);
				if ( aux!=null ) for ( String np : aux ) auxSet.add(np);
				auxSet.add(point);
				result.put(tag, auxSet.toArray(new String[0]));
			}
		}
		return sortByValue(result);
	}

	// Take the results of a Flickr query and return a map associating tags to the corresponding multipoints
	public static Map<String,String> tagsToMultipoints ( Document doc ) throws XPathExpressionException {
		Map<String,String> result = new HashMap<String,String>();
		String xpathStr = "//photo";
		XPath xpath = XPathFactory.newInstance().newXPath();
		XPathExpression expr = xpath.compile(xpathStr);
		NodeList elemList = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
		for ( int i = 0; i < elemList.getLength(); i++ ) {
			String latitude = xpath.compile("./@latitude").evaluate(elemList.item(i));
			String longitude = xpath.compile("./@longitude").evaluate(elemList.item(i));
			String point = longitude + " " + latitude;
			String tags[] = xpath.compile("./@tags").evaluate(elemList.item(i)).split(" +");
			for ( String tag : tags) {
				if (!isValidTag(tag)) continue;
				String aux = result.get(tag);
				if ( aux == null ) { 
					aux = "MULTIPOINT(" + point + ")";
				} else {
					aux = aux.substring(0,aux.length()-2);
					aux += " , " + point + " )";
				}
				result.put(tag, aux);
			}
		}
		return result;
	}

	// Retrive an image with basis on a Uniform Resource Locator
	public static Image getPhoto ( String url ) {
		try {
			return getPhoto(new URL(url));
		} catch ( Exception e ) {
			return null;
		}
	}

	// Retrive an image with basis on a Uniform Resource Locator
	public static Image getPhoto ( URL url ) {
		Image image = java.awt.Toolkit.getDefaultToolkit().getDefaultToolkit().createImage(url);
		return NaiveSimilarityFinder.toBufferedImage(image);
	}

	// Return a double value corresponding to the degree of similarity between two images
	public static double imageSimilarity ( String url1 , String url2 ) {
		Image img1 = getPhoto(url1);
		Image img2 = getPhoto(url2);
		return imageSimilarity(img1,img2);
	}

	// Return a double value corresponding to the degree of similarity between two images
	public static double imageSimilarity ( Image img1 , Image img2 ) {
		
		JFrame frame1 = new JFrame("Display image");
		JLabel panel1 = new JLabel(new ImageIcon( img1 ));
		frame1.getContentPane().add(panel1);
		frame1.setSize(500, 500);
		frame1.setVisible(true);
		
		JFrame frame2 = new JFrame("Display image");
		JLabel panel2 = new JLabel(new ImageIcon( img2 ));
		frame2.getContentPane().add(panel2);
		frame2.setSize(500, 500);
		frame2.setVisible(true);

		double distance = NaiveSimilarityFinder.imageDistance(img1,img2);
		return 1.0 / (1.0 + Math.sqrt(distance));
	}

	// Return a double value corresponding to the degree of similarity between two images
	public static double imageSimilarityCompression ( String url1 , String url2 ) {
		Image img1 = getPhoto(url1);
		Image img2 = getPhoto(url2);
		return imageSimilarityCompression(img1,img2);
	}

	// Return a double value corresponding to the degree of similarity between two images
	public static double imageSimilarityCompression ( Image img1 , Image img2 ) {
		double distance = CompressionSimilarityFinder.imageDistance(img1,img2);
		return 1.0 / (1.0 + distance);
	}
	
	// Return a double value corresponding to the degree of similarity between two images
	public static double imageSimilarityWavelet ( String url1 , String url2 ) {
		Image img1 = getPhoto(url1);
		Image img2 = getPhoto(url2);
		return imageSimilarityWavelet(img1,img2);
	}

	// Return a double value corresponding to the degree of similarity between two images
	public static double imageSimilarityWavelet ( Image img1 , Image img2 ) {
		double distance = WaveletSimilarityFinder.imageDistance(img1,img2);
		return 1.0 / (1.0 + distance);
	}

	public static double distance ( double lat1, double lon1, double lat2, double lon2 ) {
		double distance = Math.acos(Math.sin(lat1)*Math.sin(lat2) +
                          Math.cos(lat1)*Math.cos(lat2) *
                          Math.cos(lon2-lon1)) * 6371;
		return 1.0  / (1.0 + distance);
	}

	public static double distance ( String point1, String point2 ) {
    	double lon1 = new Double(point1.substring(point1.indexOf('(')+1,point1.indexOf(' ')).trim());
    	double lat1 = new Double(point1.substring(point1.indexOf(' ')+1,point1.indexOf(')')).trim());
    	double lon2 = new Double(point2.substring(point2.indexOf('(')+1,point2.indexOf(' ')).trim());
    	double lat2 = new Double(point2.substring(point2.indexOf(' ')+1,point2.indexOf(')')).trim());
		return distance(lat1,lon1,lat2,lon2);
	}

	// Main method to test the class
	public static void main ( String args[] ) throws Exception {
    	if ( Class.forName("org.apache.xerces.parsers.StandardParserConfiguration") != null) {
    		System.setProperty("org.apache.xerces.xni.parser.XMLParserConfiguration", "org.apache.xerces.parsers.StandardParserConfiguration");
    	}
//		System.out.println("*** XML DOCUMENT FOR QUERY BY TAG ***");
//		Document photosByTag = queryFlickr("sunset");
//		printXML(photosByTag);
		//System.out.println("*** XML DOCUMENT FOR QUERY BY COORDINATES ***");
		//Document photosByRadius = queryFlickr(-9.15037, 38.7257, 32);
		//printXML(photosByRadius);
		//System.out.println("*** MAPPING FROM PHOTOS TO TAGS ***");		
		//printMap(photosToTags(photosByRadius));
		//System.out.println("*** MAPPING FROM PHOTOS TO USERS ***");		
		//printMap(photosToUsers(photosByRadius));
		//System.out.println("*** MAPPING FROM PHOTOS TO POINTS ***");		
		//printMap(photosToPoints(photosByRadius));
		//System.out.println("*** MAPPING FROM PHOTOS TO NUMBER OF VIEWS ***");		
		//printMap(photosToViews(photosByRadius));
		//System.out.println("*** MAPPING FROM TAGS TO PHOTOS ***");		
		//printMap(tagsToPhotos(photosByRadius));
		//System.out.println("*** MAPPING FROM TAGS TO USERS ***");		
		//printMap(tagsToUsers(photosByRadius));
		//System.out.println("*** MAPPING FROM TAGS TO POINTS ***");		
		//printMap(tagsToPoints(photosByRadius));
		//System.out.println("*** MAPPING FROM TAGS TO MULTIPOINTS ***");		
		//printMap(tagsToMultipoints(photosByRadius));
		//System.out.println("*** MAPPING FROM TAGS TO PHOTO VIEWS ***");		
		//printMap(tagsToViews(photosByRadius));
		System.out.println("*** MEASURING IMAGE SIMILARITY ***");
		Image img0 = getPhoto(new URL("http://farm2.static.flickr.com/1128/1226915900_eea86783cd_s.jpg"));
		Image img1 = getPhoto(new URL("http://farm2.static.flickr.com/1128/1226915900_eea86783cd_z.jpg"));
		Image img2 = getPhoto(new URL("http://farm2.static.flickr.com/1128/1226915900_eea86783cd.jpg"));
		Image img3 = getPhoto(new URL("http://farm2.static.flickr.com/1179/1226784048_d634ee1bc2_m.jpg"));
		
//		System.out.println("similarity=" + imageSimilarity(img2,img2));
//		System.out.println("similarity=" + imageSimilarity(img1,img1));
		System.out.println("similarity=" + imageSimilarity(img1,img2));
//		System.out.println("similarity=" + imageSimilarity(img1,img3));
		
//		System.out.println("similarity2=" + imageSimilarityCompression(img2,img2));
//		System.out.println("similarity2=" + imageSimilarityCompression(img1,img1));
//		System.out.println("similarity2=" + imageSimilarityCompression(img1,img2));
//		System.out.println("similarity2=" + imageSimilarityCompression(img1,img3));
	}
	
}